#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/init.h>
#include <asm/io.h> 
#include <mach/platform.h>
#include <linux/gpio.h>
#include <linux/delay.h>


static struct gpio pinsLED[] = {
		{26, GPIOF_OUT_INIT_LOW, "LED_GREEN"},
		{5, GPIOF_OUT_INIT_LOW, "LED_WHITE"},
		{19, GPIOF_OUT_INIT_LOW, "LED_BLUE"},
		{21, GPIOF_OUT_INIT_LOW, "LED_RED"},
		{6, GPIOF_OUT_INIT_LOW, "LED_YELLOW"}
};

struct ledQueue{
	char* queue;
	int len;
	int speed;	
};

//stop = 2x

static struct ledQueue ledQ[] = {
	{"00000000000000000000", 5, 1},
	{"11111111111111111111", 5, 1},
	{"22222222222222222222", 5, 1},
	{"33333333333333333333", 5, 1},
	{"44444444444444444444", 5, 1},
	{"11111111111111111111", 5, 1},
	{"11111111111111111111", 5, 1},
	{"11111111111111111111", 5, 1},
	{"11111111111111111111", 5, 1},
	{"11111111111111111111", 5, 1}
	
};



static int instructionLen = 4;

static int stop = 0;
static int currentLedSeqIndex = 0;

static struct timer_list blink_timer;
static void blink_timer_func(unsigned long data)
{
	int ledQNumber = stop/2;
	int ledNumber = (int)ledQ[currentLedSeqIndex].queue[ledQNumber]-'0';
	int speed = ledQ[currentLedSeqIndex].speed;
	if(stop >= 0)
	{
		
		printk(KERN_INFO "%s\n", __func__);
		gpio_set_value(pinsLED[ledNumber].gpio, data);
		/* schedule next execution */
		blink_timer.data = !data; // makes the LED toggle
		blink_timer.expires = jiffies + (1*HZ/speed); // 1 sec.
		add_timer(&blink_timer);
		stop--;
	}
}


static dev_t first; // Global variable for the first device number
static struct cdev c_dev; // Global variable for the character device structure
static struct class *cl; // Global variable for the device class




static int my_open(struct inode *i, struct file *f)
{
  printk(KERN_INFO "Driver: open()\n");
  //clear();	
  return 0;
}
  static int my_close(struct inode *i, struct file *f)
{
  printk(KERN_INFO "Driver: close()\n");
  return 0;
}
  static ssize_t my_read(struct file *f, char __user *buf, size_t
  len, loff_t *off)
{
  

  return 0;
}
  static ssize_t my_write(struct file *f, const char __user *buf,
  size_t len, loff_t *off)
{	
	int i =0;
	int size = 0;
	int number = 0;
	int ledSeqIndex = 0;
	if(len > 4)
	{
		ledSeqIndex = (int) buf[2] - '0';
		if(ledSeqIndex < 0 || ledSeqIndex >9){
			ledSeqIndex = 0;
		}

		//instructions

		if(buf[0] == 'n' && buf[1] == 'c' && buf[3] == '=')
		{
			size = len -5;
			if(size > 9)
				size= 19;
			
			ledQ[ledSeqIndex].len = size;
			for(i = 0; i< size;i++)
			{
				
				if(buf[instructionLen+i] < '0' || buf[instructionLen+i] >'4'){
					number = 0;
				}
				
				ledQ[ledSeqIndex].queue[i] = buf[instructionLen+i];
			}
			printk(KERN_INFO "ncX=");
		} 
		else if(buf[0] == 's' && buf[1] == 'c' && buf[3] == '=' && len > 5)
		{
			number = (int) buf[4] - '0';
			if(number < 0 || number >9){
				number = 0;
			}
			ledQ[ledSeqIndex].speed = number;
			printk(KERN_INFO "scX=");
		}
		else if(buf[0] == 'r' && buf[1] == 'n' && buf[3] == '=')
		{

			currentLedSeqIndex=ledSeqIndex;
			printk(KERN_DEBUG "currentledSeq index = %d\n",currentLedSeqIndex); 
			stop = 2*ledQ[ledSeqIndex].len-1;
			printk(KERN_DEBUG "stop = %d\n",stop); 
			blink_timer.expires = jiffies + (HZ/ledQ[ledSeqIndex].speed); // 1 sec.
			printk(KERN_DEBUG "speed = %d\n",(HZ/ledQ[ledSeqIndex].speed)); 
			add_timer(&blink_timer);
			
			printk(KERN_INFO "rnX=");
		}
			
	}
	
	
	
	return len;
}
  static struct file_operations pugs_fops =
{
  .owner = THIS_MODULE,
  .open = my_open,
  .release = my_close,
  .read = my_read,
  .write = my_write
};
 
static int __init ofcd_init(void) /* Constructor */
{

	int ret = 0;


	ret = gpio_request_array(pinsLED, ARRAY_SIZE(pinsLED));

	if (ret) {
		printk(KERN_ERR "Unable to request GPIOs: %d\n", ret);
		return ret;
	}
	

  if (alloc_chrdev_region(&first, 0, 1, "Radom") < 0)
  {
    return -1;
  }
    if ((cl = class_create(THIS_MODULE, "charLed")) == NULL)
  {
    unregister_chrdev_region(first, 1);
    return -1;
  }
    if (device_create(cl, NULL, first, NULL, "ledDriver") == NULL)
  {
    class_destroy(cl);
    unregister_chrdev_region(first, 1);
    return -1;
  }
    cdev_init(&c_dev, &pugs_fops);
    if (cdev_add(&c_dev, first, 1) == -1)
  {
    device_destroy(cl, first);
    class_destroy(cl);
    unregister_chrdev_region(first, 1);
    return -1;
  }
 	
	init_timer(&blink_timer);
	blink_timer.function = blink_timer_func;
	blink_timer.data = 1L; // initially turn LED on
	
	
  return 0;
}


 
static void __exit ofcd_exit(void) /* Destructor */
{
	int i = 0;
	//set values to 0
	for(i = 0; i < ARRAY_SIZE(pinsLED); i++) {
		gpio_set_value(pinsLED[i].gpio, 0);
	}
	// unregister all GPIOs
	gpio_free_array(pinsLED, ARRAY_SIZE(pinsLED));
	//other
	cdev_del(&c_dev);
	device_destroy(cl, first);
	class_destroy(cl);
	unregister_chrdev_region(first, 1);
	printk(KERN_INFO "Alvida: ofcd unregistered");
}
 
module_init(ofcd_init);
module_exit(ofcd_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com>");
MODULE_DESCRIPTION("Our First Character Driver");